function ParseSpellEffArgs(key, effArgs, ParseEffArgs)
	local arg = gSpellEffectArgs[key]
	if not arg then
		arg = {}
		ParseEffArgs(arg, effArgs)
		gSpellEffectArgs[key] = arg
	end
	return arg
end

-- restTimes, skipTimes, firstTime
function CalcSpellBuffTriggerTimes(duration, interval, elapse, skip)
	if interval == 0 then
		return 1, 0, nil
	end
	if not elapse or not skip then
		if duration == 0 then
			return -1, 0, nil
		else
			return math.max(duration // interval, 1), 0, nil
		end
	end
	function FirstTime()
		return systime() + interval - (elapse + skip) % interval
	end
	if duration == 0 then
		function SkipTimes()
			return (elapse + skip) // interval - elapse // interval
		end
		return -1, SkipTimes(), FirstTime()
	else
		function RestTimes()
			return math.max(duration // interval, 1) -
				math.min(elapse + skip, duration) // interval
		end
		function SkipTimes()
			return math.min(elapse + skip, duration) // interval -
				math.min(elapse, duration) // interval
		end
		return RestTimes(), SkipTimes(), FirstTime()
	end
end

function CalcSpellBuffRestTime(duration, elapse, skip)
	return math.max(duration - (elapse or 0) - (skip or 0), 0)
end

function TryDetachSpellBuffAsTimerDtor(tx, key)
	if tx.handler then
		tx.handler = nil
		tx.obj:DetachSpellBuffInfo(key)
	end
end

function TryRemoveTimerWhenSpellBuffDetached(tx)
	local handler = tx.handler
	if handler then
		tx.handler = nil
		RemoveTimer(tx.obj, handler)
	end
end

function DetachSpellBuffWhenTimerExpired(tx, key)
	tx.handler = nil
	tx.obj:DetachSpellBuffInfo(key)
end

function RemoveSpellBuffTimer(tx)
	RemoveTimer(tx.obj, tx.handler)
end